package com.micro.ai.auth.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.micro.ai.auth.dto.UserSessionDTO;
import com.micro.ai.auth.entity.UserSession;
import com.micro.ai.auth.mapper.UserSessionMapper;
import com.micro.ai.auth.service.UserSessionService;
import com.micro.ai.auth.service.TokenBlacklistService;
import com.micro.ai.commons.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户会话服务实现
 * 
 * @author micro-ai
 * @since 0.0.1
 */
@Slf4j
@Service
public class UserSessionServiceImpl implements UserSessionService {

    @Autowired
    private UserSessionMapper userSessionMapper;

    @Autowired
    private TokenBlacklistService tokenBlacklistService;

    @Autowired
    private AuthServiceImpl authService;

    @Override
    public List<UserSessionDTO> getUserSessions(String userId) {
        LambdaQueryWrapper<UserSession> query = new LambdaQueryWrapper<>();
        query.eq(UserSession::getUserId, userId);
        query.gt(UserSession::getExpiresAt, LocalDateTime.now()); // 只返回未过期的会话
        query.orderByDesc(UserSession::getLastActiveAt);

        List<UserSession> sessions = userSessionMapper.selectList(query);
        return sessions.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    @Override
    public UserSessionDTO getSessionById(String sessionId) {
        UserSession session = userSessionMapper.selectById(sessionId);
        if (session == null) {
            throw new BusinessException("会话不存在");
        }
        return convertToDTO(session);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void terminateSession(String sessionId, String currentUserId) {
        UserSession session = userSessionMapper.selectById(sessionId);
        if (session == null) {
            throw new BusinessException("会话不存在");
        }

        // 验证权限（只能下线自己的会话，除非是管理员）
        if (!session.getUserId().equals(currentUserId)) {
            // 这里应该检查是否是管理员，暂时简化处理
            throw new BusinessException("无权下线他人会话");
        }

        // 将token加入黑名单（确保立即失效）
        // 使用完整的JWT token而不是数据库中的hash值
        String fullJwtToken = authService.getFullJwtToken(sessionId);
        if (StringUtils.hasText(fullJwtToken)) {
            try {
                // 将完整的access token加入黑名单，设置2小时过期时间
                tokenBlacklistService.addToBlacklist(fullJwtToken, 7200);
                log.info("会话完整token已加入黑名单: sessionId={}, token={}", sessionId, 
                    fullJwtToken.substring(0, Math.min(20, fullJwtToken.length())) + "...");
            } catch (Exception e) {
                log.error("将完整token加入黑名单失败: sessionId={}, error={}", sessionId, e.getMessage(), e);
                // 不影响下线流程，继续执行
            }
        } else {
            log.warn("未找到完整的JWT token映射: sessionId={}", sessionId);
        }

        // 将refresh token也加入黑名单
        if (StringUtils.hasText(session.getRefreshToken())) {
            try {
                // 将refresh token加入黑名单，设置7天过期时间
                tokenBlacklistService.addToBlacklist(session.getRefreshToken(), 604800);
                log.info("会话refresh token已加入黑名单: sessionId={}", sessionId);
            } catch (Exception e) {
                log.error("将refresh token加入黑名单失败: sessionId={}, error={}", sessionId, e.getMessage(), e);
                // 不影响下线流程，继续执行
            }
        }

        // 删除会话记录
        userSessionMapper.deleteById(sessionId);
        
        // 清理token映射
        authService.removeTokenMapping(sessionId);

        log.info("会话已强制下线: sessionId={}, userId={}", sessionId, session.getUserId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void terminateAllSessions(String userId, String currentSessionId) {
        LambdaQueryWrapper<UserSession> query = new LambdaQueryWrapper<>();
        query.eq(UserSession::getUserId, userId);
        if (StringUtils.hasText(currentSessionId)) {
            query.ne(UserSession::getId, currentSessionId); // 保留当前会话
        }

        // 先查询要删除的会话，将token加入黑名单
        List<UserSession> sessionsToTerminate = userSessionMapper.selectList(query);
        for (UserSession session : sessionsToTerminate) {
            // 将完整的access token加入黑名单
            String fullJwtToken = authService.getFullJwtToken(session.getId());
            if (StringUtils.hasText(fullJwtToken)) {
                try {
                    tokenBlacklistService.addToBlacklist(fullJwtToken, 7200);
                    log.info("批量下线完整token已加入黑名单: sessionId={}", session.getId());
                } catch (Exception e) {
                    log.error("批量下线完整token加入黑名单失败: sessionId={}, error={}", session.getId(), e.getMessage(), e);
                }
            } else {
                log.warn("批量下线时未找到完整的JWT token映射: sessionId={}", session.getId());
            }
            
            // 将refresh token加入黑名单
            if (StringUtils.hasText(session.getRefreshToken())) {
                try {
                    tokenBlacklistService.addToBlacklist(session.getRefreshToken(), 604800);
                } catch (Exception e) {
                    log.error("批量下线refresh token加入黑名单失败: sessionId={}, error={}", session.getId(), e.getMessage(), e);
                }
            }
        }

        // 删除会话记录
        int count = userSessionMapper.delete(query);
        
        // 清理所有相关的token映射
        for (UserSession session : sessionsToTerminate) {
            authService.removeTokenMapping(session.getId());
        }

        log.info("用户所有会话已强制下线: userId={}, count={}", userId, count);
    }

    @Override
    public Page<UserSessionDTO> listOnlineSessions(int pageNum, int pageSize, String tenantId, String keyword) {
        Page<UserSession> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<UserSession> query = new LambdaQueryWrapper<>();

        // 只查询未过期的会话
        query.gt(UserSession::getExpiresAt, LocalDateTime.now());

        if (StringUtils.hasText(tenantId)) {
            query.eq(UserSession::getTenantId, tenantId);
        }
        if (StringUtils.hasText(keyword)) {
            query.and(q -> q.like(UserSession::getUserId, keyword)
                    .or().like(UserSession::getIpAddress, keyword)
                    .or().like(UserSession::getLocation, keyword));
        }

        query.orderByDesc(UserSession::getLastActiveAt);

        Page<UserSession> sessionPage = userSessionMapper.selectPage(page, query);

        Page<UserSessionDTO> dtoPage = new Page<>(sessionPage.getCurrent(), sessionPage.getSize(), sessionPage.getTotal());
        dtoPage.setRecords(sessionPage.getRecords().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList()));

        return dtoPage;
    }

    @Override
    public Map<String, Object> getOnlineStats(String tenantId) {
        LambdaQueryWrapper<UserSession> query = new LambdaQueryWrapper<>();
        query.gt(UserSession::getExpiresAt, LocalDateTime.now());

        if (StringUtils.hasText(tenantId)) {
            query.eq(UserSession::getTenantId, tenantId);
        }

        // 总在线会话数
        long totalSessions = userSessionMapper.selectCount(query);

        // 按设备类型统计
        long webCount = getDeviceTypeCount("web", tenantId);
        long mobileCount = getDeviceTypeCount("mobile", tenantId);
        long tabletCount = getDeviceTypeCount("tablet", tenantId);
        long desktopCount = getDeviceTypeCount("desktop", tenantId);
        long otherCount = totalSessions - webCount - mobileCount - tabletCount - desktopCount;

        // 在线用户数（去重）
        List<UserSession> allSessions = userSessionMapper.selectList(query);
        long uniqueUsers = allSessions.stream()
                .map(UserSession::getUserId)
                .distinct()
                .count();

        // 构建设备统计对象
        Map<String, Object> deviceStats = new HashMap<>();
        deviceStats.put("web", webCount);
        deviceStats.put("mobile", mobileCount);
        deviceStats.put("tablet", tabletCount);
        deviceStats.put("desktop", desktopCount);
        deviceStats.put("other", otherCount);

        Map<String, Object> stats = new HashMap<>();
        stats.put("totalSessions", totalSessions);
        stats.put("activeUsers", uniqueUsers);
        stats.put("deviceStats", deviceStats);
        stats.put("locationStats", new HashMap<>()); // 位置统计，暂时为空

        return stats;
    }

    /**
     * 获取指定设备类型的会话数量
     */
    private long getDeviceTypeCount(String deviceType, String tenantId) {
        LambdaQueryWrapper<UserSession> query = new LambdaQueryWrapper<>();
        query.gt(UserSession::getExpiresAt, LocalDateTime.now());
        if (StringUtils.hasText(tenantId)) {
            query.eq(UserSession::getTenantId, tenantId);
        }
        query.eq(UserSession::getDeviceType, deviceType);
        return userSessionMapper.selectCount(query);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int cleanExpiredSessions() {
        LambdaQueryWrapper<UserSession> query = new LambdaQueryWrapper<>();
        query.le(UserSession::getExpiresAt, LocalDateTime.now());

        // 先查询要清理的过期会话，将token加入黑名单
        List<UserSession> expiredSessions = userSessionMapper.selectList(query);
        for (UserSession session : expiredSessions) {
            // 将完整的access token加入黑名单
            String fullJwtToken = authService.getFullJwtToken(session.getId());
            if (StringUtils.hasText(fullJwtToken)) {
                try {
                    tokenBlacklistService.addToBlacklist(fullJwtToken, 7200);
                    log.debug("过期会话完整token已加入黑名单: sessionId={}", session.getId());
                } catch (Exception e) {
                    log.error("过期会话完整token加入黑名单失败: sessionId={}, error={}", session.getId(), e.getMessage(), e);
                }
            } else {
                log.warn("过期会话清理时未找到完整的JWT token映射: sessionId={}", session.getId());
            }
            
            // 将refresh token加入黑名单
            if (StringUtils.hasText(session.getRefreshToken())) {
                try {
                    tokenBlacklistService.addToBlacklist(session.getRefreshToken(), 604800);
                } catch (Exception e) {
                    log.error("过期会话refresh token加入黑名单失败: sessionId={}, error={}", session.getId(), e.getMessage(), e);
                }
            }
        }

        // 删除过期会话记录
        int count = userSessionMapper.delete(query);
        
        // 清理所有相关的token映射
        for (UserSession session : expiredSessions) {
            authService.removeTokenMapping(session.getId());
        }

        log.info("清理过期会话: count={}", count);

        return count;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLastActiveTime(String sessionToken) {
        LambdaQueryWrapper<UserSession> query = new LambdaQueryWrapper<>();
        query.eq(UserSession::getSessionToken, sessionToken);

        UserSession session = userSessionMapper.selectOne(query);
        if (session != null) {
            session.setLastActiveAt(LocalDateTime.now());
            userSessionMapper.updateById(session);
        }
    }

    /**
     * 转换为DTO
     */
    private UserSessionDTO convertToDTO(UserSession session) {
        if (session == null) {
            return null;
        }
        UserSessionDTO dto = new UserSessionDTO();
        BeanUtils.copyProperties(session, dto);
        dto.setIsCurrent(false); // 默认false，需要在Controller中判断
        return dto;
    }
}

